#ifndef MODELS_H
#define MODELS_H

#include <cstdio>
#include <vector>
#include <cmath>

#include <GL/gl.h>
#include <GL/glu.h>

#include "primitive.h"
#include "trace.h"
#include "cursor.h"

using namespace std;

class Ground : public Primitive{

    private:
        int id;
        vector<Point> points;
        
    public:
        Ground(){
            this-> id = 0;
            this->type = ENVIROMENT;
            
            points.push_back( Point(-200.0, 0.0, -200.0) );
            points.push_back( Point(+200.0, 0.0, -200.0) );
            points.push_back( Point(+200.0, 0.0, +200.0) );
            points.push_back( Point(-200.0, 0.0, +200.0) );    
                    
            r = 0.0;
            g = 1.0;
            b = 0.0;
            a = 1.0;
        }
        ~Ground(){}
        
        void setId(int _id){
            this->id = _id;
        }
        
        int getId(){
            return this->id;
        }

        void draw(){
            glColor4f(r, g, b, a);
            glLoadName(this->id);
            glBegin(GL_POLYGON);
                for (int i=0; i<points.size(); i++){
                    glVertex3f( points[i].getX(), 
                                points[i].getY(), 
                                points[i].getZ() );
                }
            glEnd();
        
            GLfloat fExtent = 200.0f;
            GLfloat fStep = 1.0f;
            GLfloat y = 0.001f;
            GLfloat iLine;
            
            glLineWidth(1);
            glColor3f(0.0, 0.0, 0.0);
            glBegin(GL_LINES);
               for(iLine = -fExtent; iLine <= fExtent; iLine += fStep)
                  {
                  glVertex3f(iLine, y, fExtent);
                  glVertex3f(iLine, y, -fExtent);
            
                  glVertex3f(fExtent, y, iLine);
                  glVertex3f(-fExtent, y, iLine);
                  }
            glEnd();
        }

};

class Plane : public Primitive{

    private:
        Point normal;
        Point center;
        
        vector<Point> calculatePoints(){
            vector<Point> points;
            
            if (normal.getZ() < -0.1 or normal.getZ() > 0.1 ){
                points.push_back( Point( -200, -200, 
                                         - normal.getX() * (-200) / normal.getZ()
                                         - normal.getY() * (-200) / normal.getZ()) );

                points.push_back( Point( +200, -200, 
                                         - normal.getX() * (+20) / normal.getZ()
                                         - normal.getY() * (-20) / normal.getZ()) );
                                         
                points.push_back( Point( +200, +200, 
                                         - normal.getX() * (+200) / normal.getZ()
                                         - normal.getY() * (+200) / normal.getZ()) );
                                         
                points.push_back( Point( -200, +200, 
                                         - normal.getX() * (-200) / normal.getZ()
                                         - normal.getY() * (+200) / normal.getZ()) );
            }
            else if (normal.getY() < -0.1 or normal.getY() > 0.1 ){
                points.push_back( Point( -200, -200, 
                                         - normal.getX() * (-200) / normal.getY()
                                         - normal.getZ() * (-200) / normal.getY()) );

                points.push_back( Point( +200, -200, 
                                         - normal.getX() * (+200) / normal.getY()
                                         - normal.getZ() * (-200) / normal.getY()) );
                                         
                points.push_back( Point( +200, +200, 
                                         - normal.getX() * (+200) / normal.getY()
                                         - normal.getZ() * (+200) / normal.getY()) );
                                         
                points.push_back( Point( -200, +200, 
                                         - normal.getX() * (-200) / normal.getY()
                                         - normal.getZ() * (+200) / normal.getY()) );
            }
            else if (normal.getX() < -0.1 or normal.getX() > 0.1 ){
                points.push_back( Point( -200, -200, 
                                         - normal.getY() * (-200) / normal.getX()
                                         - normal.getZ() * (-200) / normal.getX()) );

                points.push_back( Point( +200, -200, 
                                         - normal.getY() * (+200) / normal.getX()
                                         - normal.getZ() * (-200) / normal.getX()) );
                                         
                points.push_back( Point( +200, +200, 
                                         - normal.getY() * (+200) / normal.getX()
                                         - normal.getZ() * (+200) / normal.getX()) );
                                         
                points.push_back( Point( -200, +200, 
                                         - normal.getY() * (-200) / normal.getX()
                                         - normal.getZ() * (+200) / normal.getX()) );
            }
            for (int i = 0; i < points.size(); i++){
                points[i] += center;
            }
            return points;        
        }        
        
    public:
        Plane(){
            this->type = ENVIROMENT;
        
            normal = Point(0.0, 1.0, 0.0);
            center = Point(0.0, 0.0, 0.0);
            r = 0.9;
            g = 0.8;
            b = 0.0;
            a = 1.0;
        }
        
        Plane(Point _normal, Point _center){
            this->type = ENVIROMENT;
        
            normal = _normal;
            center = _center;
            r = 0.9;
            g = 0.8;
            b = 0.0;
            a = 1.0;        
        }
        
        void setCenter(Point _c){
            this->center = _c;
        }

        void setNormal(Point _n){
            this->normal = _n;
        }
        
        void show(){
            printf("PLANE:\n\tCenter: ");
            center.show();
            printf("\tNormal: ");
            normal.show();
        }
        
        void draw(){
            vector<Point> points = calculatePoints();
            glColor4f(r, g, b, a);
            glBegin(GL_POLYGON);
                for (int i=0; i<points.size(); i++){
                    glVertex3f( points[i].getX(), points[i].getY(), points[i].getZ() );
                }
            glEnd();
        }
};


class Sphere{
    private:
        float radius;
        Point center;
        
    public:
        Sphere(Point _center, float _radius){
            this->center = _center;
            this->radius = _radius;
        }
        
        void draw(){
            glPushMatrix();
                glTranslatef(center.getX(), center.getY(), center.getZ());
	            GLUquadricObj *pObj;
	            pObj = gluNewQuadric();
	            gluQuadricNormals(pObj, GLU_SMOOTH);
	            gluSphere(pObj, radius, 25, 15);
	            gluDeleteQuadric(pObj);
            glPopMatrix();        
        }
};


class Cylinder{
    private:
        float radius;
        Point center;
        Point generatrix;
        
    public:
        Cylinder(Point _center, float _radius, Point _generatrix){
            this->center = _center;
            this->radius = _radius;
            this->generatrix = _generatrix;
        }
        
        void rotate(){
            Point up(0.0, 0.0, generatrix.lenght());
            Point normal = generatrix.crossProduct(up);
                        
            float angle = acos( generatrix.dotProduct(up) 
                                / ( generatrix.lenght() * up.lenght() ) );
            
            glRotatef(-angle/3.14159265*180, normal.getX(), normal.getY(), normal.getZ());
        }
        
        void draw(){
            glPushMatrix();
                glTranslatef(center.getX(), center.getY(), center.getZ());
                rotate();
	            GLUquadricObj *pObj;
	            pObj = gluNewQuadric();
	            gluQuadricNormals(pObj, GLU_SMOOTH);
	            gluCylinder(pObj, radius, radius, generatrix.lenght(), 25, 15);
	            gluDeleteQuadric(pObj);
            glPopMatrix();        
        }
};

class Segment{
    private:
        Point origin;
        Point destination;
        
    public:
        Segment(Point _origin, Point _destination){
            this->origin = _origin;
            this->destination = _destination;
        }
        
        pair<Point, Point> getVertices(){
            pair<Point, Point> p;
            p.first = origin;
            p.second = destination;
            return p;
        }
        
        void draw(){
            glPushMatrix();
                glColor3f(0.0, 1.0, 0.0);
                Sphere o(this->origin, 10.0/25.0);
                o.draw();
                
                Sphere d(this->destination, 10.0/25.0);
                d.draw();
                
                glColor3f(1.0, 0.0, 0.0);
                Cylinder c(origin, 8.0/25.0, destination-origin);
                c.draw();

            glPopMatrix();
        }
        
        bool operator==(Segment s){
            pair<Point, Point> p = s.getVertices();
            if ( (p.first.distance(origin) < 0.1 and p.second.distance(destination) < 0.1)
            or (p.second.distance(origin) < 0.1 and p.first.distance(destination) < 0.1) ){
                return true;
            }
            else{
                return false;
            }
        }
};


class Polygon{
    private:
        vector<Point> vertices;
        
    public:
        Polygon(){}
        Polygon(vector<Point> _vertices){
            this->vertices = _vertices;
        }
        
        void addVertex(Point v){
            vertices.push_back(v);
        }
        
        void draw(float r, float g, float b){
            if (vertices.size() > 0){
                GLUtesselator *pTess = gluNewTess();
                
                glColor3f(r, g, b); 

                gluTessCallback(pTess, GLU_TESS_VERTEX, (GLvoid(*)()) &glVertex3dv);
                gluTessCallback(pTess, GLU_TESS_BEGIN, (GLvoid(*)()) &glBegin);
                gluTessCallback(pTess, GLU_TESS_END, (GLvoid(*)()) &glEnd);
                
                gluTessBeginPolygon(pTess, NULL);
                gluTessBeginContour(pTess);

                GLdouble p[this->vertices.size()][3];
                for(int i = 0; i < this->vertices.size(); i++){
                    p[i][0] = vertices[i].getX();
                    p[i][1] = vertices[i].getY();
                    p[i][2] = vertices[i].getZ();
                    gluTessVertex(pTess, p[i], p[i]); 
                }
                    
                gluTessEndContour(pTess);
                gluTessEndPolygon(pTess);
                
                gluDeleteTess(pTess);
            }
        }
};

class DrawerTrace{
    private:
        Trace trace;

    public:
        DrawerTrace(){}
        ~DrawerTrace(){}
        
        void initTrace(){
            trace.clear();
        }
        
        void addCursor(Cursor c){
            c.setSceneY(c.getSceneY() + 0.1);
            this->trace.addCursor(c);
        }
        
        void setTrace(Trace &t){
            this->trace = t;
        }
        
        void draw(){
            glColor3f(0.5f, 0.2f, 0.2f);
            glLineWidth(3);
            glBegin(GL_LINE_STRIP);
                vector<Cursor> &cursors = trace.getCursors();
                for (int i = 0; i < cursors.size(); i++ ){
                    glVertex3f( cursors[i].getSceneX(),
                                cursors[i].getSceneY(),
                                cursors[i].getSceneZ() );
                }
                            
            glEnd();            
        }
};

class Cube{
    private:
        vector<Face> faces;

    public:
        Cube(vector<Face> _faces){
            this->faces = _faces;
        }
        ~Cube(){}
        
        void draw(){
        }        
};


//class NewCube : public Object{
//    private:
//        vector< vector<Point> > polygons;
//        
//        Vertex v1, v2, v3, v4, v5, v6, v7, v8;
//        Edge e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12;
//        Face f1, f2, f3, f4, f5, f6;

//    public:
//    
//        NewCube(int _id = -1){
//            this->id = _id;
//        
//            this->v1 = Vertex(-3.0, 0.1, +3.0, 1);
//            this->v2 = Vertex(+3.0, 0.1, +3.0, 2);
//            this->v3 = Vertex(+3.0, 6.1, +3.0, 3);
//            this->v4 = Vertex(-3.0, 6.1, +3.0, 4);
//            this->v5 = Vertex(-3.0, 0.1, -3.0, 5);
//            this->v6 = Vertex(+3.0, 0.1, -3.0, 6);
//            this->v7 = Vertex(+3.0, 6.1, -3.0, 7);
//            this->v8 = Vertex(-3.0, 6.1, -3.0, 8);
//            
//            this->v1.setColor(0.0, 1.0, 0.0);
//            this->v1.setSize(9);
//            this->v2.setColor(0.0, 1.0, 0.0);
//            this->v2.setSize(9);
//            this->v3.setColor(0.0, 1.0, 0.0);
//            this->v3.setSize(9);
//            this->v4.setColor(0.0, 1.0, 0.0);
//            this->v4.setSize(9);
//            this->v5.setColor(0.0, 1.0, 0.0);
//            this->v5.setSize(9);
//            this->v6.setColor(0.0, 1.0, 0.0);
//            this->v6.setSize(9);
//            this->v7.setColor(0.0, 1.0, 0.0);
//            this->v7.setSize(9);
//            this->v8.setColor(0.0, 1.0, 0.0);
//            this->v8.setSize(9);
//            
//            this->e1 = Edge(v1, v2, 101);
//            this->e2 = Edge(v2, v3, 102);
//            this->e3 = Edge(v3, v4, 103);
//            this->e4 = Edge(v4, v1, 104);
//            this->e5 = Edge(v2, v6, 105);
//            this->e6 = Edge(v6, v7, 106);
//            this->e7 = Edge(v7, v3, 107);
//            this->e8 = Edge(v7, v8, 108);
//            this->e9 = Edge(v8, v4, 109);
//            this->e10 = Edge(v8, v5, 110);
//            this->e11 = Edge(v5, v1, 111);
//            this->e12 = Edge(v5, v6, 112);


//            this->e1.setColor(1.0, 0.0, 0.0);
//            this->e1.setSize(8);
//            this->e2.setColor(1.0, 0.0, 0.0);
//            this->e2.setSize(8);
//            this->e3.setColor(1.0, 0.0, 0.0);
//            this->e3.setSize(8);
//            this->e4.setColor(1.0, 0.0, 0.0);
//            this->e4.setSize(8);
//            this->e5.setColor(1.0, 0.0, 0.0);
//            this->e5.setSize(8);
//            this->e6.setColor(1.0, 0.0, 0.0);
//            this->e6.setSize(8);
//            this->e7.setColor(1.0, 0.0, 0.0);
//            this->e7.setSize(8);
//            this->e8.setColor(1.0, 0.0, 0.0);
//            this->e8.setSize(8);
//            this->e9.setColor(1.0, 0.0, 0.0);
//            this->e9.setSize(8);
//            this->e10.setColor(1.0, 0.0, 0.0);
//            this->e10.setSize(8);
//            this->e11.setColor(1.0, 0.0, 0.0);
//            this->e11.setSize(8);
//            this->e12.setColor(1.0, 0.0, 0.0);
//            this->e12.setSize(8);
//        
//            this->f1 = Face(1001);
//            this->f1.addEdge(e1);
//            this->f1.addEdge(e2);
//            this->f1.addEdge(e3);
//            this->f1.addEdge(e4);
//            
//            this->f2 = Face(1002);
//            this->f2.addEdge(e2);
//            this->f2.addEdge(e5);
//            this->f2.addEdge(e6);
//            this->f2.addEdge(e7);

//            this->f3 = Face(1003);
//            this->f3.addEdge(e3);
//            this->f3.addEdge(e7);
//            this->f3.addEdge(e8);
//            this->f3.addEdge(e9);
//            
//            this->f4 = Face(1004);
//            this->f4.addEdge(e4);
//            this->f4.addEdge(e9);
//            this->f4.addEdge(e10);
//            this->f4.addEdge(e11);
//            
//            this->f5 = Face(1005);
//            this->f5.addEdge(e6);
//            this->f5.addEdge(e8);
//            this->f5.addEdge(e10);
//            this->f5.addEdge(e12);
//            
//            this->f6 = Face(1006);
//            this->f6.addEdge(e1);
//            this->f6.addEdge(e5);
//            this->f6.addEdge(e12);
//            this->f6.addEdge(e11);
//            
//            this->f1.setColor(0.0, 0.5, 0.5, 1.0);
//            this->f2.setColor(0.0, 0.5, 0.5, 1.0);
//            this->f3.setColor(0.0, 0.5, 0.5, 1.0);
//            this->f4.setColor(0.0, 0.5, 0.5, 1.0);
//            this->f5.setColor(0.0, 0.5, 0.5, 1.0);
//            this->f6.setColor(0.0, 0.5, 0.5, 1.0);
//            
//            this->addFace(f1);
//            this->addFace(f2);
//            this->addFace(f3);
//            this->addFace(f4);
//            this->addFace(f5);
//            this->addFace(f6);
//        }
//        ~NewCube(){}
//};

#endif /* MODELS_H */
